<?php

namespace app\common\library;


/**
 * 微信类
 */
class Wechatpay
{
    /**
     * 请求地址
     * @var string
     */
    public $method = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
    public $refundMethod = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
    /**
     * 应用唯一标识
     * @var string
     */
    public $appId;
    /**
     * 商户ID
     * @var string
     */
    public $mchId;
    /**
     * 商户key
     * @var string
     */
    public $key;
    /**
     * 签名类型
     * @var string
     */
    public $signType = 'md5';
    /**
     * cert
     * @var string
     */
    public $cert;
    /**
     * ssl_key
     * @var string
     */
    public $ssl_key;
    /**
     * 是否强制校验真实姓名 FORCE_CHECK强制
     * @var string
     */
    public $check_name = 'NO_CHECK';
    /**
     * 支付回调地址
     * @var string
     */
    public $notifyUrl;
    public $payType;
    public $openId;
    public function __construct()
    {
        $this->appId = \think\config::get("site.wechatappid");
        $this->mchId = \think\config::get("site.wechatmachid");
        $this->key = \think\config::get("site.wechatmachkey");
        $this->notifyUrl = request()->domain()."api/notify/wechat";
    }
    /**
     * 统一下单
     * @param $data
     * @return array
     * @throws \Exception
     */
    public function pay($data) {
        try {
            if(empty($this->appId) || empty($this->mchId) || empty($this->key)){
                throw new \Exception("参数缺失");
            }
            $params = [
                'body' => $data['body'],
                'out_trade_no' => $data['orderId'],
                'total_fee' => bcmul($data['payPrice'], 100,0),//单位:分
            ];
            $data = $this->getData($params);
            $response = $this->postXmlCurl($data, $this->method);
            $arr = $this->xml2array($response);
            $pack = 'Sign=WXPay';
            $prePayParams = [];
            $prePayParams['appid'] = isset($arr['appid']) ? $arr['appid'] : "";
            $prePayParams['noncestr'] = isset($arr['nonce_str']) ? $arr['nonce_str'] : "";
            $prePayParams['partnerid'] = isset($arr['mch_id']) ? $arr['mch_id'] : "";
            $prePayParams['prepayid'] = isset($arr['prepay_id']) ? $arr['prepay_id'] : "";
            $prePayParams['package'] = $pack;
            $prePayParams['timestamp'] = time();
            ksort($prePayParams);
            $prePayParams['sign'] = $this->generateSign($prePayParams, $this->key);
            $prePayParams['timestamp'] = (string)$prePayParams['timestamp'];
            return $prePayParams;
        } catch (\Exception $e) {
            throw new \Exception('微信统一下单失败:'.$e->getMessage());
        }
    }
    /**
     * 原路退款
     */
    public function refund($info)
    {
        $params = [
            'appid' => $this->appId,
            'mch_id' => $this->mchId,
            'nonce_str' => \fast\Utils::random(32),
            'transaction_id' => $info['transaction_id'],
            'out_refund_no' => $info['orderId'],
            'total_fee' => bcmul($info['payPrice'] , 100,0),
            'refund_fee' => bcmul($info['returnPrice'] , 100,0),
        ];
        ksort($params);
        $params['sign'] = strtoupper(md5($this->ToUrlParams($params)."&key=".$this->key));
        $data = self::arr2xml($params);
        $response = $this->postXmlCurl($data, $this->refundMethod, true);
        return $this->xml2array($response);
    }
    /**
     * 格式化请求参数
     * @param $params
     * @return string
     */
    private function getData($params,$type = '') {
        $base = [
            'appid' => $this->appId,
            'mch_id' => $this->mchId,
            'nonce_str' => \fast\Utils::random(32),
            'spbill_create_ip' => \fast\Utils::getIp(),
            'notify_url' => $this->notifyUrl,
            'trade_type' => 'APP',
        ];
        $params = array_filter(array_merge($base, $params));
        ksort($params);
        $params['sign'] = $this->generateSign($params, $this->key);
        return self::arr2xml($params);
    }
    /**
     * Generate a signature.
     *
     * @param array $attributes
     * @param string $key
     * @param string $encryptMethod
     *
     * @return string
     */
    public function generateSign(array $attributes, $key, $encryptMethod = 'md5') {
        $attributes['key'] = $key;
        return strtoupper(call_user_func_array($encryptMethod, [urldecode(http_build_query($attributes))]));
    }
    /**
     * @param $xml
     * @return mixed
     * @throws Exception
     */
    private function xml2array($xml) {
        if (!$xml) {
            throw new Exception("xml数据异常！");
        }
        //将XML转为array
        libxml_disable_entity_loader(true); //禁止引用外部xml实体
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }

    /**
     * @param $array
     * @return string
     * @throws Exception
     */
    private static function arr2xml($array) {
        if (!is_array($array) || count($array) <= 0) {
            throw new Exception("数组数据异常！");
        }
        $xml = "<xml>";
        foreach ($array as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<".$key.">".$val."</".$key.">";
            } else {
                $xml .= "<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }


    /**
     * @param $xml
     * @param $url
     * @param bool $useCert
     * @param int $second
     * @return bool|mixed
     */
    private function postXmlCurl($xml, $url, $useCert = false, $second = 30) {
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, false);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        if ($useCert == true) {
            //设置证书
            //使用证书：cert 与 key 分别属于两个.pem文件
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
            //curl_setopt($ch,CURLOPT_SSLCERT, WxPayConfig::SSLCERT_PATH);
            curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
            //curl_setopt($ch,CURLOPT_SSLKEY, WxPayConfig::SSLKEY_PATH);
        }
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            return false;
        }
    }
    /*
     * 验签
     */
    public function checkSign($data) {
        ksort($data);
        $string1 = '';
        foreach ($data as $k => $v) {
            if ($v != '' && $k != 'sign') {
                $string1 .= "{$k}={$v}&";
            }
        }
        $sign = strtoupper(md5($string1."key={$this->key}"));
        if ($sign == $data['sign']) {
            return true;
        }
        return false;
    }
    /**
     * 格式化参数格式化成url参数
     */
    public function ToUrlParams($params) {
        $buff = "";
        foreach ($params as $k => $v) {
            if ($k != "sign" && $v != "" && !is_array($v)) {
                $buff .= $k."=".$v."&";
            }
        }
        $buff = trim($buff, "&");
        return $buff;
    }
}
